home *** CD-ROM | disk | FTP | other *** search
/ Disc to the Future 2 / Disc to the Future Part II Programmer's Reference (Wayzata Technology)(6013)(1992).bin / MAC / THINKC / 4_0 / SOUND_EX.AMP < prev    next >
Text File  |  1991-08-16  |  31KB  |  942 lines

  1. In the interest of helping more people to grok the
  2. beast that is the Sound Manager, I'm posting this 
  3. sample C code (I use THINK C 4.0).  It's translated
  4. from the Pascal code in Apple's SoundApp example 
  5. package, and seems to work well.  With this code, you
  6. can do three things:
  7.  
  8. 1.  Play up to three channels of async sound.  It's
  9.     trivial to add the fourth;  I just didn't need it.
  10. 2.  Continuously play an async sound by waiting for
  11.     the callBack routine to alert you that the previous
  12.     sound is done and starting a new one.
  13. 3.  Play a "song" consisting of different sound samples
  14.     by using the sound queue.  The code I include is
  15.     hard-coded for my needs and will need to be modified
  16.     for your particular sound needs.  This code plays
  17.     6 different samples in a specific ordering, then
  18.     loops through the last two samples forever.  
  19.     
  20.  
  21. Disclaimer: the Apple code is good.  What I've modified
  22. probably is not.  This isn't meant to teach you how to
  23. program well.  It took me a hell of a long time to learn
  24. how to do this stuff, simply because everyone is so
  25. tight-lipped about using the Sound Manager.  If I had
  26. had this sample code when I started this program, life
  27. would have been much simpler, and I hope that this makes
  28. life simpler for someone else out there.
  29.   I've been running this code under System 7, but I think
  30. that this stuff will work under some versions of system
  31. 6.0.x too, esp. 6.0.7.  
  32.  
  33. Toby Smith
  34. bluecow@unix.cis.pitt.edu
  35.  
  36.  
  37.  
  38.  
  39. Notes about the code:
  40.  
  41. gSoundCool is a global variable I use to determine whether we should
  42. bother trying to do multi-channel sound.  Use the SysEnvRec to 
  43. determine what machine and system you're running, and set gSoundCool
  44. accordingly.
  45.  
  46. Before using any of these routines, be sure to call InitSoundUnit()
  47. first.
  48.  
  49. SingIt(a bunch of Handles to SND resources...) is my routine to play 
  50. a song consisting of sampled sounds.  It queues up a bunch of sounds
  51. to be played, as well as a callBack command when the queue is 
  52. getting low.  When I receive the callBack flag, I send more 
  53. commands to the queue to keep it full (so the song plays 
  54. forever).  For example,
  55.  
  56. SingIt(...)
  57. if (gCalledBack) {
  58.     gCalledBack = FALSE;
  59.     LoadSndQueue2();
  60. }
  61.  
  62. PlayIt(Handle) is the routine to play an async sound.  When the 
  63. sound completes, gCalledBack will be set to TRUE.  If you check
  64. gCalledBack often enough, you can issue another PlayIt() command
  65. so that it sounds like a continuous stream of sound.  However,
  66. this requires a lot of checking to avoid gaps.  It's much easier
  67. to use SingIt().  With SingIt(), you can do a lot of processing
  68. and just check gCalledBack occasionally, sending another bunch
  69. of commands as needed.  If you're using really short samples,
  70. embed the callBack command earlier on in your command queue,
  71. to give you more time to realize that you need to send more 
  72. commands.
  73.   If you don't want to do a continuous stream of commands, you
  74. should still check gCalledBack, since you should kill the
  75. sound channel (using KillSound()) as soon as you don't need it
  76. anymore.
  77.  
  78. PlayIt2() & PlayIt3() do the same thing as PlayIt(), but uses
  79. different sound channels.  By playing different sounds using
  80. these different commands, you can have multi-channel sound
  81. going.  Note: PlayIt2() and PlayIt3() also set the the same
  82. global variable, gCalledBack, when they complete their sounds.
  83. This means that gCalledBack becomes completely worthless during
  84. multi-channel playback.  An easy solution is to create a 
  85. different callBack routine for each of the three routines, and
  86. have a different global variable for each.  I didn't need this
  87. ability, so it's not in this code, but it's a trivial change.
  88.  
  89. That's about it.  Hope this is helpful.
  90.  
  91.  
  92.  
  93.  
  94.  
  95. #include <SoundMgr.h>
  96. #include <limits.h>
  97.  
  98. extern Boolean            soundIsOn, gSoundCool;
  99.  
  100.  
  101. /*These are used as flags in the sound channel to determine the state
  102.  of that channel.  The 'snth' IDs are used when the channel is in use
  103.  to determine what that channel in intended for.*/
  104. #define    kNoSynth             0                /*no synth is specified*/
  105. #define    kChanComplete         -1                /*channel has completed*/
  106. #define    kChanFree             LONG_MAX        /*channel is not in use*/
  107. #define    kSoundComplete         0x1234            /*flag for callBackCmd*/
  108.  
  109. #define    kInitNone             0                /*no init options*/
  110. #define    kWait                 FALSE            /*wait for the channel*/
  111. #define    kSMAsynch             TRUE            /*asynchronous Sound Manager call*/
  112. #define NIL                    0L
  113.  
  114. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  115.  
  116. /*I have declared a few TYPEs below to help examine 'snd ' resources.
  117.  I have to break them up into individual pieces because they are
  118.  variable sized records.*/
  119.  
  120. typedef struct Snd1Header                        
  121.     {
  122.         int         format;                    
  123.         int            numSynths;                    
  124.     } Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl;
  125.  
  126. typedef struct Snd2Header
  127.     {
  128.         int            format;
  129.         int            refCount;
  130.     } Snd2Header;
  131.     
  132.  
  133.  
  134. typedef struct SynthInfo                        
  135.     {
  136.         int         synthID;                    
  137.         long        initOption;                    
  138.     } SynthInfo, *SynthInfoPtr;
  139.  
  140. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  141.  
  142. /*I have created my own sound channel type.  It extends the normal
  143.  sound channel by adding a few fields.  To confirm that the channel
  144.  in question is mine, I set the userInfo field to something that I
  145.  can recognize.  I keep the 'snd ' resource handle associated to this
  146.  channel too.  This allows me to dispose of the data once the channel
  147.  has completed its duties.*/
  148.  
  149. typedef struct MyChanType                        /*this is a 1064 byte structure*/
  150.     {
  151.         SndChannel    theChan;                    /*must keep sound channel as first field*/
  152.         Handle        dataHandle;                    
  153.     } MyChanType, *MyChanPtr;
  154.  
  155. /*I╒ve had to re-define the SoundHeader to correctly match the current version.
  156.  This is correctly defined in MPW 3.2 or later.*/
  157.  
  158. typedef struct MySndHeader {
  159.     Ptr                    samplePtr;
  160.     long                length;
  161.     Fixed                sampleRate;
  162.     long                loopStart;
  163.     long                loopEnd;
  164.     short                encode;
  165.     short                baseNote;
  166.     char                sampleArea[];
  167. } MySndHeader, *MySndHeaderPtr;
  168.  
  169.  
  170. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  171.  
  172. /*These four global pointers are to my sound channels.  One of them has to
  173.  be global and I could utilize the fact that they are a linked list.  That
  174.  would complicate things to some degree, and it really wouldn╒t have that
  175.  much of an advantage.*/
  176.  
  177.     extern MyChanPtr    gChan1;
  178.     extern MyChanPtr    gChan2;
  179.     extern MyChanPtr    gChan3;
  180.     extern MyChanPtr    gChan4;
  181.  
  182. /*This is the global flag that is set once the sound channel╒s call back
  183.  procedure has been called.  This, in my case, means the channel has
  184.  completed its duties and is time for disposing.*/
  185.  
  186.     extern Boolean         gCalledBack;
  187.  
  188. /*gChanOpen is a flag set to determine if the application has a sound
  189.  channel open.  It╒s really not feasible to determine if a sound is being
  190.  made at any given point.*/
  191.  
  192.      extern Boolean        gChanOpen;
  193.  
  194.  
  195. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  196. Boolean InitMyChan()
  197. {
  198.     gChan1 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
  199.     if (gChan1 != NIL) {
  200.         (*gChan1).theChan.qLength = stdQLength;
  201.         (*gChan1).theChan.userInfo = kChanFree;
  202.     } else
  203.         return(FALSE);
  204.     gChan2 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
  205.     if (gChan2 != NIL) {
  206.         (*gChan2).theChan.qLength = stdQLength;
  207.         (*gChan2).theChan.userInfo = kChanFree;
  208.     } else
  209.         return(FALSE);
  210.     gChan3 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
  211.     if (gChan3 != NIL) {
  212.         (*gChan3).theChan.qLength = stdQLength;
  213.         (*gChan3).theChan.userInfo = kChanFree;
  214.     } else
  215.         return(FALSE);
  216.     gChan4 = (MyChanPtr)(NewPtrClear(sizeof(MyChanType)));
  217.     if (gChan4 != NIL) {
  218.         (*gChan4).theChan.qLength = stdQLength;
  219.         (*gChan4).theChan.userInfo = kChanFree;
  220.     } else
  221.         return(FALSE);
  222.  
  223.     return(TRUE);
  224. }
  225.  
  226.  
  227. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  228. OSErr InitSoundUnit()
  229.  
  230. /*Create storage for each of the four channels (4 * 1064 bytes) and
  231.  initialize them.  If any of the channels cannot be allocated, return an
  232.  error.  Also initialized our global flag ╥gCalledBack.╙ An interesting
  233.  modification would be to allow the caller to pass in the number of
  234.  channels the application really intends on using. If the user only wants
  235.  one channel, then we could just allocate one instead of four.  These
  236.  channels are used at interrupt time.*/
  237.  
  238. {
  239.     gChanOpen = FALSE;
  240.     gCalledBack = FALSE;
  241.     if (InitMyChan() == TRUE)
  242.         return(noErr);
  243.     else
  244.         return(MemError() );
  245. }
  246.  
  247.  
  248. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  249. OSErr HoldSnd(Handle sndHandle)
  250.  
  251. /*This is used to put the given sound resource into memory and hold it there.
  252.  I also use a MoveHHi to keep the heap from being fragmented.  If this
  253.  fails, then I return an error.  I dereference the handle and check if the
  254.  master pointer is NIL.  This would mean the data could not be loaded.*/
  255.  
  256. {
  257.     if (sndHandle != NIL) {
  258.         LoadResource(sndHandle);
  259.         if ((*sndHandle) == NIL)
  260.             return(nilHandleErr);                    /*master pointer is NIL*/
  261.         else {
  262.             MoveHHi(sndHandle);
  263.             HLock(sndHandle);
  264.             return(noErr);
  265.         }
  266.     } else
  267.         return(nilHandleErr);                            /*user passed a NIL handle*/
  268. }
  269.  
  270.  
  271. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  272. OSErr SendQuiet( SndChannelPtr chan, Boolean immediate )
  273.  
  274. /*Given a channel, this will place a quietCmd into the channel╒s queue.  I
  275.  use SndDoCommand with the noWait flag set to wait for the channel to
  276.  accept the command in the case it is currently full.
  277.  
  278.  BUG NOTE: A sequence of notes and rests will not work unless quietCmds
  279.  are between them.  Rests have to be made quiet before they rest, if that
  280.  makes any more sense.  A noteCmd will loop, causing the sound in progress
  281.  to continue, until a quietCmd is received. */
  282.  
  283. {
  284.     SndCommand theCmd;
  285.  
  286.     theCmd.cmd = quietCmd;
  287.     theCmd.param1 = 0;
  288.     theCmd.param2 = 0;
  289.     if (immediate)
  290.         return(SndDoImmediate(chan, &theCmd));
  291.     else
  292.         return(SndDoCommand(chan, &theCmd, kWait));
  293. }
  294.  
  295.  
  296. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  297. void FreeChan( MyChanPtr myChan )
  298.  
  299. /*Test if the channel is free, and if not then call SndDisposeChannel.
  300.  This will release the synthesizer (snth resource) code and the
  301.  required hardware.  If we didn╒t do this, no other channels would
  302.  work.  I also test myChan for having a snd resource attached to the
  303.  channel.  If so, then I mark it as purgeable and reset the data to NIL.
  304.  
  305.  BUG NOTE: Calling SndDisposeChannel while or immediately after playing
  306.  a sequence of notes would often hang/crash a non-Apple Sound Chip based Mac.
  307.  Issuing a quietCmd first kept the Sound Manager happy and my Mac from
  308.  crashing. */
  309.  
  310. {
  311.     OSErr theErr;
  312.  
  313.     if ((*myChan).theChan.userInfo != kChanFree) {
  314.         theErr = SendQuiet((SndChannelPtr)(myChan), !kWait);    /*ignore error*/
  315.         theErr = SndDisposeChannel((SndChannelPtr)(myChan), !kWait);
  316.         (*myChan).theChan.userInfo = kChanFree;
  317.     }
  318.     if ((*myChan).dataHandle != NIL) {
  319.         HUnlock((*myChan).dataHandle);
  320.         HPurge((*myChan).dataHandle);
  321.         (*myChan).dataHandle = NIL;
  322.     }
  323. }
  324.  
  325.  
  326. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  327. Boolean SoundCompletion()
  328.  
  329. /*This routine can be called to determine if the sound has completed.  When
  330.  this is true, the sound data can be disposed of.  The global ╥gCalledBack╙
  331.  determines whether the sound has completed or not and it is set by the
  332.  sound channel╒s completion routine.  Soundcompletion is placed in the Main
  333.  segment because it is called by the event loop.*/
  334.  
  335. {
  336.     return(gCalledBack);
  337. }
  338.  
  339.  
  340. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  341. void DoSoundComplete()
  342.  
  343. /*This routine is called by an application that established a sound to be
  344.  played asynchronously.  This is, in effect, the routine to be used after
  345.  the completion routine has been called.  The application should call this
  346.  once SoundCompletion returns TRUE.  In the case the application is using
  347.  multiple channels, we will only free a channel once it has been marked as
  348.  kChanComplete.  I do not reset the gCalledBack until all the open channels
  349.  are freed.*/
  350.  
  351. {
  352.     if ((*gChan1).theChan.userInfo == kChanComplete)
  353.         FreeChan(gChan1);
  354.     if ((*gChan2).theChan.userInfo == kChanComplete)
  355.         FreeChan(gChan2);
  356.     if ((*gChan3).theChan.userInfo == kChanComplete)
  357.         FreeChan(gChan3);
  358.     if ((*gChan4).theChan.userInfo == kChanComplete)
  359.         FreeChan(gChan4);
  360.  
  361.     if (((*gChan1).theChan.userInfo == kChanFree)
  362.      && ((*gChan2).theChan.userInfo == kChanFree)
  363.      && ((*gChan3).theChan.userInfo == kChanFree)
  364.      && ((*gChan4).theChan.userInfo == kChanFree)) {
  365.          gCalledBack = FALSE;
  366.         gChanOpen = FALSE;                                /*no longer making noises*/
  367.     }
  368. }
  369.  
  370.  
  371. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  372. void FreeAllChans()
  373.  
  374. /*This is the routine that will force all channels to be released.  It also
  375.  resets the gCalledBack flag.  This is used by all routines just before
  376.  opening a new channel to force all channels to be disposed. */
  377.  
  378. {
  379.     FreeChan(gChan1);
  380.     FreeChan(gChan2);
  381.     FreeChan(gChan3);
  382.     FreeChan(gChan4);
  383.     gCalledBack = FALSE;
  384.     gChanOpen = FALSE;                             /*no longer making noises*/
  385. }
  386.  
  387.  
  388.  
  389. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  390. void KillSound()
  391.  
  392. /*This is used to free up all the sound data and channels in use.  There are
  393.  two reasons for using this routine.  One is after a sound has completed
  394.  the other is to terminate a sound that may be in progress.  For this
  395.  second reason, this routine should always be used before playing a new
  396.  sound.  */
  397.  
  398. {
  399.     if (SoundCompletion() )                             /*why were we called?*/
  400.         DoSoundComplete();                                /*from completion*/
  401.     else
  402.         FreeAllChans();                                    /*or just because*/
  403. }
  404.  
  405. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  406. OSErr SoundComplete( SndChannelPtr chan )
  407.  
  408. /*I use this to install the callBackCmd into the given channel.  I need to
  409.  pass in our A5 along with the command so that the callBack routine can
  410.  access my globals.  I also wait until the channel is ready for another
  411.  command in the case of the channel being full.  Once the Sound Manager
  412.  calls my call back procedure I will dispose of the channel.  So, this is
  413.  the last sound command to be sent to a channel.  I pass to the call back
  414.  A5 in the second parameter of the callBackCmd.  Refer to Tech Note #208.*/
  415.  
  416. {
  417.     SndCommand theCmd;
  418.  
  419.  
  420.     theCmd.cmd = callBackCmd;
  421.     theCmd.param1 = kSoundComplete;
  422.     theCmd.param2 = SetCurrentA5();
  423.     return(SndDoCommand(chan, &theCmd, kWait));
  424. }
  425.  
  426.  
  427. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  428. OSErr SndDataAvailable(Handle sndHandle)
  429.  
  430. /*Given a resource handle, this will attempt to load it into memory.  If the
  431.  data is not available, then return an error.*/
  432.  
  433. {
  434.     if (sndHandle != NIL) {
  435.         LoadResource(sndHandle);
  436.         if (*sndHandle == NIL)
  437.             return(nilHandleErr);            /*master pointer is NIL*/
  438.     } else
  439.         return(nilHandleErr);                /*user passed a NIL handle*/
  440.     return(noErr);
  441. }
  442.  
  443.  
  444. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  445. SynthInfo GetSynthInfo(Handle sndHandle)
  446.  
  447. /*This routine will return the 'snth' resource ID specified by the sound.
  448.  I use this to determine if the given sound will work with _SndPlay.
  449.  This routine does not require the data to be in memory when called.  It
  450.  also doesn╒t lock it down while looking for the information.  I will
  451.  mark the resource as being purgeable in the case the resource attributes
  452.  has it╒s non-purgeable bit set.*/
  453.  
  454. {
  455.     Ptr            soundPtr;
  456.     OSErr        theErr;
  457.     SynthInfo    ourSynth;
  458.     
  459.     ourSynth.synthID = kNoSynth;
  460.     ourSynth.initOption = 0;
  461.     theErr = SndDataAvailable(sndHandle);
  462.     if (theErr == noErr) {
  463.         soundPtr = *sndHandle;
  464.         if ((*(Snd1HdrPtr)(soundPtr)).format == firstSoundFormat) {
  465.             if ((*(Snd1HdrPtr)(soundPtr)).numSynths != kNoSynth) {
  466.                 soundPtr = (Ptr)(soundPtr + sizeof(Snd1Header));
  467.                 ourSynth.synthID = (*(SynthInfoPtr)(soundPtr)).synthID;
  468.                 ourSynth.initOption = (*(SynthInfoPtr)(soundPtr)).initOption;
  469.             }
  470.         } else {                             /*snd is a format 2 for HyperCard*/
  471.             ourSynth.synthID = sampledSynth;
  472.             ourSynth.initOption = 0;        /*no options currently supported*/
  473.         }
  474.     HPurge(sndHandle); 
  475.     }
  476.     
  477.     return(ourSynth);
  478. }
  479.  
  480.  
  481. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  482. pascal void DoCallBack(SndChannelPtr chan, SndCommand *theCmd )
  483.  
  484. /*This will be called at interrupt time by the Sound Manager when it
  485.  receives a callBackCmd.  I use the second parameter of the command to hold
  486.  my application╒s A5 reference.  I first set up A5 so that I can access my
  487.  globals.  I mark the given channel as being complete and set gCalledBack
  488.  to TRUE.  This lets the application know that the callBackCmd has been
  489.  processed.  The callBackCmd can be used for other purposes, and the first
  490.  parameter of the command could be a flag to a more extensive routine.
  491.  Synchronizing the application with the channel is possible with this
  492.  method.
  493.  
  494.  WARNING: This routine MUST be resident in memory and cannot make a call
  495.  to a non-resident segment.  I put this into the Main segment because of
  496.  this.
  497.  
  498.  BUG NOTE: System 6.0.4 has a bug in _SndPlay when using a sampled sound
  499.  'snd '.  A bogus callBackCmd is placed into the queue immediately after
  500.  the bufferCmd used to play the sound.  This bogus callBackCmd will cause
  501.  my callBackProc to be called when I wasn╒t expecting it.  I have been
  502.  using the command╒s second parameter to contain my A5 address.  If I╒m
  503.  given a bogus callBackCmd, it would be really bad to set A5 address to
  504.  this bogus parameter in the command.  I found that the bogus callBackCmd
  505.  contains the handle to the 'snd ' passed in to _SndPlay.  I also found
  506.  that param1 contains the handle╒s state bits (results of HGetState).  To
  507.  work with this bug I set my real callBackCmd╒s param1 to a specific value
  508.  when I installed it into the queue.  See the SoundComplete routine.  Then
  509.  I test the callBackCmd to make sure I╒m dealing with the real one.*/
  510.  
  511. {
  512.     long theA5;
  513.  
  514.     if ((*theCmd).param1 == kSoundComplete) {                /*if it╒s my callBackCmd*/
  515.         theA5 = SetA5((*theCmd).param2);                     /*refer to tech note 208*/
  516.         (*chan).userInfo = kChanComplete;                    /*this channel is done*/
  517.         gCalledBack = TRUE;
  518.         theA5 = SetA5(theA5);                            /*restore original A5*/
  519.     }
  520. }
  521.  
  522.  
  523. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  524. Boolean IsMyChan(SndChannelPtr chan)
  525. {
  526.     return(((chan == (SndChannelPtr)(gChan1))                     /*is it one of ours?*/
  527.                 || (chan == (SndChannelPtr)(gChan2))
  528.                 || (chan == (SndChannelPtr)(gChan3))
  529.                 || (chan == (SndChannelPtr)(gChan4))));
  530. }
  531.  
  532. Boolean CompatibleChan(SndChannelPtr waveChan)
  533. {
  534.     return(((*waveChan).userInfo == waveTableSynth)                /*wave or..*/
  535.                         || ((*waveChan).userInfo == kChanFree));    /*free chan*/
  536. }
  537.  
  538.  
  539. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  540. OSErr ChanAvailable(SndChannelPtr theChan)
  541.  
  542. /*This routine will test the given channel to see if it will really produce
  543.  sound.  The Sound Manager in System 6.0x will return noErr even if the
  544.  channel isn╒t going to work.  In the future the Sound Manager will return
  545.  the proper error.  Until then, I use this routine to determine this for me.
  546.  There can only be a single channel at any time, unless I have the wave
  547.  table synthesizer open.  This will allow four channels.  Channels have
  548.  a pointer to the next channel, and if this is not NIL I suspect the
  549.  given channel will not work.  I test the given channel for being a
  550.  wave type, and if so I need to see if the other channels I╒ve got are
  551.  also wave type.  If it doesn╒t look like the channels is available, I
  552.  return badChannel.  It is important to set the userInfo field of a channel
  553.  before calling this routine!
  554.  
  555.  BUG NOTE: If an application is not using the Sound Manager and instead
  556.  uses the older Sound Driver, any given channel will fail.  Or if the other
  557.  application does not release is channels, then my channels will not work.
  558.  The most noticeable offender of this is HyperCard.  Friendly applications
  559.  will dispose of their channels at suspend/resume times or ASAP.*/
  560.  
  561. {
  562.     if ((*theChan).nextChan != NIL) {                                /*looks bad*/
  563.         if ((*theChan).userInfo == waveTableSynth) {                /*last attempt*/
  564.             if (IsMyChan((*theChan).nextChan)
  565.              && CompatibleChan((SndChannelPtr)(gChan1))
  566.              && CompatibleChan((SndChannelPtr)(gChan2))
  567.              && CompatibleChan((SndChannelPtr)(gChan3))
  568.              && CompatibleChan((SndChannelPtr)(gChan4)))
  569.                     return(noErr);                                    /*got lucky*/
  570.                 else    
  571.                     return(badChannel);                        
  572.         }
  573.     }
  574.     return(noErr);
  575. }
  576.  
  577.  
  578. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  579. OSErr GetNoSynthChan()
  580.  
  581. /*This is the routine to create a channel that isn╒t associated with any
  582.  synthesizer.  Why? Because if you wanted to use _SndPlay asynchronously
  583.  you need to get such a channel.  When I create a channel I will set the
  584.  userInfo field marking it with the 'snth' associated to it.  This must be
  585.  done before calling ChanAvailable.
  586.  
  587.  BUG NOTE: Do not use a channel already associated to a snth with
  588.  _SndPlay.  This causes the Sound Manager to install a second copy of the
  589.  same snth.
  590.  
  591.  I didn't understand why it had any parameters for my purposes so I removed
  592.  them since they were being a pain in my butt anyway. */
  593.  
  594. {
  595.     OSErr theErr;
  596.  
  597.     FreeChan(gChan1);
  598.     theErr = SndNewChannel(&gChan1, kNoSynth,
  599.                             kInitNone, DoCallBack);
  600.     if (theErr == noErr) {
  601.         (*gChan1).theChan.userInfo = kNoSynth;
  602.         theErr = ChanAvailable((SndChannelPtr)(gChan1));
  603.     }
  604.     if (theErr != noErr)
  605.         FreeChan(gChan1);
  606.     else
  607.         gChanOpen = TRUE;                                /*now we╒re making noise*/
  608.     return(theErr);
  609. }
  610.  
  611.  
  612. /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
  613. void PlayIt(Handle sndHandle)
  614.  
  615. /*Given a sound resource, this routine will call _SndPlay.  The snd must
  616.  be either a format 2 or format 1 that contains snth information.
  617.  Using _SndPlay asynchronously requires us to lock the snd prior to
  618.  calling the trap.  The reason being is _SndPlay remembers the state of the
  619.  lock bit using _HGetState and _HSetState.  If the snd is unlocked when
  620.  it╒s passed to _SndPlay, it will be unlocked again when _SndPlay exits.
  621.  This would be bad when using the sound asynchronously.  If the sound being
  622.  passed in happens to be a compressed sound created with MACE, it will ╥do
  623.  the right thing.╙  If MACE isn╒t around the Sound Manager will pretend to
  624.  play a sound but nothing will be heard.
  625.  
  626.  BUG NOTE:  The sampled sound synthesizer in System 6.0x does not check for
  627.  a Memory Manager error when allocating its internal buffer.  There is a
  628.  call to NewPtr(1316) and if a NIL is return, the Sound Manager will write
  629.  randomly to memory.  Also, the pointer is allocated into the application╒s
  630.  heap instead of the system╒s.
  631.  
  632.  BUG NOTE:  _SndPlay when using System 6.0.4 and a sampled sound will send
  633.  a bogus callBackCmd into the channel.  This will cause the user╒s call
  634.  back procedure to be called as soon as the sound has completed.  Refer
  635.  to the DoCallBack routine for details. */
  636.  
  637. {
  638. OSErr        theErr;
  639. int            synthID;
  640.  
  641.     if (!soundIsOn) 
  642.         return;
  643.  
  644.     theErr = HoldSnd(sndHandle);                /* hold on to the sound */
  645.     if (theErr == noErr) {
  646.         theErr = GetNoSynthChan();
  647.         (*gChan1).dataHandle = sndHandle;        /* so FreeAllChans can dispose of data */
  648.         if (theErr == noErr) {
  649.             synthID = GetSynthInfo(sndHandle).synthID;
  650.             if (synthID != kNoSynth) {
  651.                 (*gChan1).theChan.userInfo = synthID; 
  652.                 
  653.                 theErr = SndPlay((SndChannelPtr)(gChan1), sndHandle, kSMAsynch); 
  654.                 if (theErr == noErr)
  655.                     theErr = SoundComplete((SndChannelPtr)(gChan1));
  656.             } else
  657.                 theErr = badFormat;
  658.         }
  659.     }
  660.     if (theErr != noErr)
  661.         FreeChan(gChan1);
  662. }
  663.  
  664.  
  665. OSErr Get2NoSynthChan()
  666. {
  667.     OSErr theErr;
  668.  
  669.     FreeChan(gChan2);
  670.     theErr = SndNewChannel(&gChan2, kNoSynth,
  671.                             kInitNone, DoCallBack);
  672.     if (theErr == noErr) {
  673.         (*gChan2).theChan.userInfo = kNoSynth;
  674.         theErr = ChanAvailable((SndChannelPtr)(gChan2));
  675.     }
  676.     if (theErr != noErr)
  677.         FreeChan(gChan2);
  678.     else
  679.         gChanOpen = TRUE;                                /*now we╒re making noise*/
  680.     return(theErr);
  681. }
  682.  
  683.  
  684. void PlayIt2(Handle sndHandle)
  685. {
  686. OSErr        theErr;
  687. int            synthID;
  688.  
  689.     if (!soundIsOn) 
  690.         return;
  691.      if (!gSoundCool) {
  692.          PlayIt(sndHandle);
  693.          return;
  694.      }
  695.          
  696.     theErr = HoldSnd(sndHandle);                /* hold on to the sound */
  697.     if (theErr == noErr) {
  698.         theErr = Get2NoSynthChan();
  699.         (*gChan2).dataHandle = sndHandle;        /* so FreeAllChans can dispose of data */
  700.         if (theErr == noErr) {
  701.             synthID = GetSynthInfo(sndHandle).synthID;
  702.             if (synthID != kNoSynth) {
  703.                 (*gChan2).theChan.userInfo = synthID; 
  704.                 
  705.                 theErr = SndPlay((SndChannelPtr)(gChan2), sndHandle, kSMAsynch); 
  706.                 if (theErr == noErr)
  707.                     theErr = SoundComplete((SndChannelPtr)(gChan2));
  708.             } else
  709.                 theErr = badFormat;
  710.         }
  711.     }
  712.     if (theErr != noErr)
  713.         FreeChan(gChan2);
  714. }
  715.  
  716.  
  717. OSErr Get3NoSynthChan()
  718. {
  719.     OSErr theErr;
  720.  
  721.     FreeChan(gChan3);
  722.     theErr = SndNewChannel(&gChan3, kNoSynth,
  723.                             kInitNone, DoCallBack);
  724.     if (theErr == noErr) {
  725.         (*gChan3).theChan.userInfo = kNoSynth;
  726.         theErr = ChanAvailable((SndChannelPtr)(gChan3));
  727.     }
  728.     if (theErr != noErr)
  729.         FreeChan(gChan3);
  730.     else
  731.         gChanOpen = TRUE;                                /*now we╒re making noise*/
  732.     return(theErr);
  733. }
  734.  
  735.  
  736. void PlayIt3(Handle sndHandle)
  737. {
  738. OSErr        theErr;
  739. int            synthID;
  740.  
  741.     if (!soundIsOn) 
  742.         return;
  743.      if (!gSoundCool) {
  744.          PlayIt(sndHandle);
  745.          return;
  746.      }
  747.  
  748.     theErr = HoldSnd(sndHandle);                /* hold on to the sound */
  749.     if (theErr == noErr) {
  750.         theErr = Get3NoSynthChan();
  751.         (*gChan3).dataHandle = sndHandle;        /* so FreeAllChans can dispose of data */
  752.         if (theErr == noErr) {
  753.             synthID = GetSynthInfo(sndHandle).synthID;
  754.             if (synthID != kNoSynth) {
  755.                 (*gChan3).theChan.userInfo = synthID; 
  756.                 
  757.                 theErr = SndPlay((SndChannelPtr)(gChan3), sndHandle, kSMAsynch); 
  758.                 if (theErr == noErr)
  759.                     theErr = SoundComplete((SndChannelPtr)(gChan3));
  760.             } else
  761.                 theErr = badFormat;
  762.         }
  763.     }
  764.     if (theErr != noErr)
  765.         FreeChan(gChan3);
  766. }
  767.  
  768.  
  769. long GetSndDataOffset(Handle );
  770. void LoadSndQueue();
  771.  
  772. static SndCommand    theCmd1, theCmd2, theCmd3, theCmd4, theCmd5, theCmd6;
  773.  
  774. void SingIt(Handle sndHandle1, Handle sndHandle2, Handle sndHandle3, Handle sndHandle4, 
  775.             Handle sndHandle5, Handle sndHandle6) 
  776. {
  777. OSErr            theErr;
  778. int                synthID;
  779. MySndHeaderPtr    dataPtr;
  780. long            dataOffset;
  781.  
  782.     theErr = HoldSnd(sndHandle1);                /* hold on to the sound */
  783.     theErr = HoldSnd(sndHandle2);                /* hold on to the sound */
  784.     theErr = HoldSnd(sndHandle3);                /* hold on to the sound */
  785.     theErr = HoldSnd(sndHandle4);                /* hold on to the sound */
  786.     theErr = HoldSnd(sndHandle5);                /* hold on to the sound */
  787.     theErr = HoldSnd(sndHandle6);                /* hold on to the sound */
  788.     if (theErr == noErr) {
  789.         theErr = SndNewChannel(&gChan1, sampledSynth, initMono, DoCallBack);
  790.         (*gChan1).dataHandle = sndHandle1;        /* so FreeAllChans can dispose of data */
  791.         if (theErr == noErr) {
  792.             dataOffset = GetSndDataOffset(sndHandle1);
  793.             dataPtr = (MySndHeaderPtr)((long)(*sndHandle1) + dataOffset);
  794.             theCmd1.cmd = bufferCmd;
  795.             theCmd1.param1 = 0;
  796.             theCmd1.param2 = (long)(dataPtr);
  797.         }
  798.         if (theErr == noErr) {
  799.             dataOffset = GetSndDataOffset(sndHandle2);
  800.             dataPtr = (MySndHeaderPtr)((long)(*sndHandle2) + dataOffset);
  801.             theCmd2.cmd = bufferCmd;
  802.             theCmd2.param1 = 0;
  803.             theCmd2.param2 = (long)(dataPtr);
  804.         }
  805.         if (theErr == noErr) {
  806.             dataOffset = GetSndDataOffset(sndHandle3);
  807.             dataPtr = (MySndHeaderPtr)((long)(*sndHandle3) + dataOffset);
  808.             theCmd3.cmd = bufferCmd;
  809.             theCmd3.param1 = 0;
  810.             theCmd3.param2 = (long)(dataPtr);
  811.         }
  812.         if (theErr == noErr) {
  813.             dataOffset = GetSndDataOffset(sndHandle4);
  814.             dataPtr = (MySndHeaderPtr)((long)(*sndHandle4) + dataOffset);
  815.             theCmd4.cmd = bufferCmd;
  816.             theCmd4.param1 = 0;
  817.             theCmd4.param2 = (long)(dataPtr);
  818.         }
  819.         if (theErr == noErr) {
  820.             dataOffset = GetSndDataOffset(sndHandle5);
  821.             dataPtr = (MySndHeaderPtr)((long)(*sndHandle5) + dataOffset);
  822.             theCmd5.cmd = bufferCmd;
  823.             theCmd5.param1 = 0;
  824.             theCmd5.param2 = (long)(dataPtr);
  825.         }
  826.         if (theErr == noErr) {
  827.             dataOffset = GetSndDataOffset(sndHandle6);
  828.             dataPtr = (MySndHeaderPtr)((long)(*sndHandle6) + dataOffset);
  829.             theCmd6.cmd = bufferCmd;
  830.             theCmd6.param1 = 0;
  831.             theCmd6.param2 = (long)(dataPtr);
  832.         }
  833.         if (theErr == noErr)
  834.             LoadSndQueue();
  835.     }
  836.     if (theErr != noErr)
  837.         FreeChan(gChan1);
  838. }
  839.  
  840. typedef int * IntPtr ;
  841. typedef SndCommand *    SndCmdPtr;
  842.  
  843. long GetSndDataOffset(Handle sndHandle)
  844. {
  845. int    waveLength, dataType, synths, howManyCmds;
  846. Ptr    cruisePtr;
  847. long myreturn;
  848.  
  849.     myreturn = 0;
  850.     dataType = kNoSynth;
  851.     waveLength = 0;
  852.     cruisePtr = *sndHandle;
  853.     
  854.     if (cruisePtr != 0) {
  855.         if ((*((Snd1HdrPtr)cruisePtr)).format == firstSoundFormat) {
  856.             synths = (*((Snd1HdrPtr)cruisePtr)).numSynths;
  857.             cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(Snd1Header));
  858.             cruisePtr = (Ptr)((long)(cruisePtr) + (sizeof(SynthInfo) * synths));
  859.         } else        
  860.             cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(Snd2Header));
  861.             
  862.         howManyCmds = *((IntPtr)(cruisePtr));    
  863.         cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(howManyCmds));
  864.  
  865.         do {                    
  866.             switch ((*((SndCmdPtr)(cruisePtr))).cmd) {
  867.  
  868.             case (soundCmd + dataPointerFlag):
  869.             case (bufferCmd + dataPointerFlag):
  870.                 dataType = sampledSynth;
  871.                 myreturn = (*((SndCmdPtr)(cruisePtr))).param2;
  872.                 howManyCmds = 0;                    
  873.                 break;
  874.             case (waveTableCmd + dataPointerFlag):
  875.                 dataType = waveTableSynth;
  876.                 waveLength = (*((SndCmdPtr)(cruisePtr))).param1;
  877.                 myreturn = (*((SndCmdPtr)(cruisePtr))).param2;
  878.                 howManyCmds = 0;                    
  879.                 break;
  880.             default:                                    
  881.                 cruisePtr = (Ptr)((long)(cruisePtr) + sizeof(SndCommand));
  882.                 howManyCmds = howManyCmds - 1;
  883.                 break;
  884.             }
  885.         } while (howManyCmds >= 1);                        
  886.     }
  887.     
  888.     return(myreturn);
  889. }
  890.  
  891.  
  892. void LoadSndQueue()
  893. {
  894. SndCommand    myCmd;
  895.  
  896.     myCmd.cmd = callBackCmd;
  897.     myCmd.param1 = kSoundComplete;
  898.     myCmd.param2 = SetCurrentA5();
  899.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  900.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  901.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  902.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  903.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  904.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  905.     SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE); 
  906.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  907.     SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE); 
  908.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  909.     SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE); 
  910.     SndDoCommand((SndChannelPtr)gChan1, &theCmd2, FALSE); 
  911.     SndDoCommand((SndChannelPtr)gChan1, &theCmd1, FALSE); 
  912.     SndDoCommand((SndChannelPtr)gChan1, &theCmd4, FALSE); 
  913.     SndDoCommand((SndChannelPtr)gChan1, &theCmd5, FALSE); 
  914.     SndDoCommand((SndChannelPtr)gChan1, &theCmd5, FALSE); 
  915.     SndDoCommand((SndChannelPtr)gChan1, &myCmd, FALSE); 
  916.     SndDoCommand((SndChannelPtr)gChan1, &theCmd5, FALSE); 
  917. }
  918.  
  919.  
  920. void LoadSndQueue2()
  921. {
  922. SndCommand    myCmd;
  923.  
  924.     myCmd.cmd = callBackCmd;
  925.     myCmd.param1 = kSoundComplete;
  926.     myCmd.param2 = SetCurrentA5();
  927.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  928.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  929.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  930.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  931.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  932.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  933.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  934.     SndDoCommand((SndChannelPtr)gChan1, &theCmd3, FALSE); 
  935.     SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE); 
  936.     SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE); 
  937.     SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE); 
  938.     SndDoCommand((SndChannelPtr)gChan1, &myCmd, FALSE); 
  939.     SndDoCommand((SndChannelPtr)gChan1, &theCmd6, FALSE); 
  940. }
  941.  
  942.